Skip to content

feat(search): in-memory encrypted message search#871

Open
Just-Insane wants to merge 14 commits into
SableClient:devfrom
Just-Insane:feat/encrypted-search-memory
Open

feat(search): in-memory encrypted message search#871
Just-Insane wants to merge 14 commits into
SableClient:devfrom
Just-Insane:feat/encrypted-search-memory

Conversation

@Just-Insane
Copy link
Copy Markdown
Contributor

@Just-Insane Just-Insane commented May 19, 2026

Description

Add a search backend that scans in-memory timelines for encrypted rooms, with room/DM scope filtering, member picker with avatars and display names, and a > quick-switcher prefix to trigger message search from the room switcher.

Fixes #

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

AI disclosure:

  • Fully AI generated (explain what all the generated code does in moderate detail).
  • Partially AI assisted (clarify which code was AI assisted and briefly explain what it does).

The search hook iterates the room's loaded in-memory timeline, filters to decrypted m.room.message events, and runs a case-insensitive includes check on the plaintext body. Results are paged client-side by slicing the filtered array. The member picker fetches m.room.member state events for the selected room and maps them to { userId, displayName, avatarUrl } entries. Typing > in the room quick-switcher input switches the autocomplete mode to message search and scopes results to the currently active room.

Split the search room list into encrypted / plaintext buckets.
Server search covers plaintext rooms unchanged. Encrypted rooms are
searched synchronously against their in-memory live timeline so
decrypted content is always available.

Key details:
- partitionRoomsByEncryption() splits the room filter; for global
  search (rooms=undefined) all joined encrypted rooms are scanned
- In-memory results are merged into the first page only (no
  pagination token for local results)
- For 'recent' order, groups are interleaved by timestamp; for
  'rank' order, server results come first
- An info banner is shown when encrypted rooms were searched so
  users know coverage is limited to cached messages
- Controlled by features.encryptedSearch in config.json (default true)
- 18 unit tests covering matching, filtering, partitioning, merging
…arch

- Adds 'Encrypted Room Search' toggle to Settings > Experimental
- Setting defaults to true; operator can hard-disable via
  config.json features.encryptedSearch = false
- Lock icon shown next to encrypted rooms in the search room picker
  when the feature is active, indicating local-cache coverage
- useMessageSearch now checks both the operator flag and user setting
Removes the guard that hid the search icon in encrypted room headers.
Encrypted rooms now navigate to message search pre-filtered to that
room, showing in-memory results when the feature is enabled.
Tooltip reads "Search (local cache)" for encrypted rooms.
- Fix DM rooms missing from search: replace useRooms (excludes DMs)
  with useSelectedRooms+isRoom selector so DM room IDs pass URL param
  validation; room picker always uses the full allRooms list
- Add SearchHasType (image/file/audio/video/link) to searchEncryptedRooms.ts
  with mEventMatchesHasTypes filtering in in-memory timeline search
- Add hasTypes to MessageSearchParams; pass contains_url:true for has:link
  on server requests; post-filter server results by msgtype/URL pattern
- Add HasFilterChips and SelectSenderButton components to SearchFilters;
  new has: row with Image/File/Audio/Video/Link toggles plus From: sender
  chips with Matrix ID input popup
- Wire has URL param through MessageSearch: parse, encode, pass to
  SearchFilters and msgSearchParams; add handleHasTypesChange/handleSendersChange
- Fix mDirects undefined crash in MessageSearch (re-add atom import)
- Allow has: filters to trigger search without a text term
  - searchEncryptedRooms: skip body text check when lowerTerm is empty
  - useMessageSearch: only early-return when both term and hasTypes are absent
  - When no term: skip server search (server requires search_term), in-memory only
  - MessageSearch: enable query when hasTypes is set even without a term
- Add DM search page at /direct/search/
  - DIRECT_SEARCH_PATH constant in paths.ts
  - getDirectSearchPath() helper in pathUtils.ts
  - useDirectSearchSelected() hook in useDirectSelected.ts
  - DirectSearch component (scoped to DM rooms)
  - Route registered in Router.tsx
  - 'Message Search' nav item added to Direct Messages panel
  - RoomViewHeader: clicking search in a DM navigates to DM search
…display names; fix DM create button alignment
Typing > in the room search modal switches to message search mode.
A 'Search messages: <query>' item appears; pressing Enter or clicking
it navigates to the context-appropriate message search page with the
term pre-filled:
- /direct/ context → DM message search
- /:spaceIdOrAlias/ context → space message search
- /home/ or other → home message search

The hint text is updated to include > for messages. The prefix is
disabled when the modal is used for room-picking (forwarding).
@Just-Insane Just-Insane marked this pull request as ready for review May 20, 2026 02:48
@Just-Insane Just-Insane requested review from 7w1 and hazre as code owners May 20, 2026 02:48
Copilot AI review requested due to automatic review settings May 20, 2026 02:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an in-memory message search backend for encrypted rooms (using locally-decrypted timeline events), expands message-search filtering (room scope, has: media types, and sender selection with avatars), and wires message search into navigation (Direct-search route + > quick switcher).

Changes:

  • Add encrypted-room in-memory search (plus merging with server search for unencrypted rooms) and expose it behind an operator feature flag + experimental user setting.
  • Add message-search UX enhancements: has: chips, sender picker, and > prefix in the room switcher to jump to message search.
  • Add Direct Messages message-search route/page and navigation affordances.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/app/state/settings.ts Adds encryptedSearch setting with default value.
src/app/pages/Router.tsx Registers Direct message search route.
src/app/pages/pathUtils.ts Adds getDirectSearchPath() helper.
src/app/pages/paths.ts Adds DIRECT_SEARCH_PATH and has search param support.
src/app/pages/client/direct/Search.tsx New DM-scoped Message Search page wrapper.
src/app/pages/client/direct/index.ts Exports DirectSearch.
src/app/pages/client/direct/Direct.tsx Adds sidebar nav entry linking to DM message search.
src/app/hooks/useClientConfig.ts Adds features.encryptedSearch operator flag.
src/app/hooks/router/useDirectSelected.ts Adds route selection hook for Direct search.
src/app/features/settings/experimental/Experimental.tsx Surfaces encrypted-search toggle in Experimental settings.
src/app/features/settings/experimental/EncryptedSearch.tsx Implements encrypted-search setting tile gated by operator flag.
src/app/features/search/Search.tsx Adds > quick-switcher mode to navigate to message search.
src/app/features/room/RoomViewHeader.tsx Enables search button in encrypted rooms when encrypted-search is enabled; routes Direct rooms to Direct search path.
src/app/features/message-search/useMessageSearch.ts Adds hasTypes, in-memory encrypted search, and server+memory merge logic.
src/app/features/message-search/SearchFilters.tsx Adds has: chips and sender picker with avatars; marks encrypted rooms in room picker.
src/app/features/message-search/searchEncryptedRooms.ts Implements timeline scanning + encryption partitioning + merge helpers.
src/app/features/message-search/searchEncryptedRooms.test.ts Adds unit tests for new encrypted search helper module.
src/app/features/message-search/MessageSearch.tsx Adds has parsing, query enabling without term, and local-cache search messaging; passes new filter props.
src/app/components/user-avatar/UserAvatar.tsx Reuses shared avatar SVG processing hook for user avatars.
src/app/components/room-avatar/AvatarImage.tsx Introduces module-level SVG blob URL cache + shared processing hook.
config.json Enables operator feature flag for encrypted search.
.changeset/encrypted-search-memory.md Changeset entry for the new feature.
Comments suppressed due to low confidence (1)

src/app/features/message-search/MessageSearch.tsx:299

  • The empty-state condition only checks !msgSearchParams.term. If a search is running with has: filters but no term, this will show the "Search Messages" hero while results are loading. Update the empty/loading conditions to account for hasTypes (and other non-term filters) so the UI reflects that a search is in progress.
      {!msgSearchParams.term && status === 'pending' && (
        <PageHeroEmpty>
          <PageHeroSection>
            <PageHero
              icon={<Icon size="600" src={Icons.Message} />}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app/features/message-search/searchEncryptedRooms.ts
Comment thread src/app/components/room-avatar/AvatarImage.tsx
Comment on lines +138 to +142
// In-memory search only runs on the first page — encrypted rooms have no pagination.
const inMemoryGroups =
encryptedSearchEnabled && isFirstPage && encryptedRoomIds.length > 0
? searchEncryptedRoomsInMemory(mx, term ?? '', encryptedRoomIds, senders, hasTypes)
: [];
Comment thread src/app/features/message-search/MessageSearch.tsx Outdated
Comment thread src/app/features/search/Search.tsx Outdated
Comment thread src/app/hooks/useClientConfig.ts Outdated
Comment thread src/app/features/message-search/searchEncryptedRooms.ts
Comment thread src/app/features/message-search/MessageSearch.tsx
- Skip events without a valid event_id in searchRoomTimeline instead of
  using an empty-string fallback (avoids key collisions and non-clickable results)
- Fix global search mode to pass allRooms to SearchFilters instead of the
  scoped rooms list (allows selecting any joined room when global=true)
- Use useSelectedSpace() for message-search URL inference in Search.tsx
  (avoids misidentifying /explore, /inbox, etc. as space IDs)
- Clarify useClientConfig.ts JSDoc: encryptedSearch is an operator kill-switch
  defaulting to enabled; the user setting defaults to disabled
- Derive isSearching from term||hasTypes for consistent UI active/loading states
- Add hasTypes filter unit tests covering all has: types (image, file, audio,
  video, link regex), OR logic, combined term+hasTypes, and exclusion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants